package main

import (
	"fmt"
	"math/rand"
	"time"
)

// ==========for循环==========

func main() {
	// for循环的第一种使用方法
	/*
		for 变量初始化; 循环判断条件; 变量迭代 {
		    // 循环执行语句
		}
	*/

	// 循环变量i放在for里面, 则for循环结束后无法再使用变量i, 这种方法可以在后续代码中再次声明变量i
	// 循环变量i是int类型
	for i := 0; i < 10; i++ {
		fmt.Println(123)
	}
	// fmt.Println(i)  错误

	i := 0
	for ; i < 10; i++ {
		fmt.Println(i * i)
	}
	fmt.Println("循环结束后的i是", i*i)

	/*
		for循环的第二种使用方式
		for 循环判断条件 {
		    // 循环执行语句
		}
		将变量初始化和变量迭代写到其他位置
	*/

	j := 1        // 循环变量初始化
	for j <= 10 { // 循环条件
		fmt.Println("j =", j)
		j++ // 循环变量迭代
	}

	/*
		for循环的第三种使用方式
		for {
		    // 循环执行语句
		}
		等价于for ; ; {
		}
		它是一个死循环, 通常需要配合break来使用
	*/
	k := 5
	for {
		if k < 10 {
			fmt.Println("k =", k)
			k++
		} else {
			break
		}
		// k++  或者k++写在这边
	}

	fmt.Println("------------------")

	// for遍历字符串
	// 1) 传统方式
	var str = "max"
	for i := 0; i < len(str); i++ {
		fmt.Printf("%c\n", str[i])
	}
	fmt.Println("------------------")
	// 2) for range
	for index, value := range str {
		fmt.Printf("%d, %c\n", index, value)
	}
	fmt.Println("------------------")
	// 3) 用传统方式打印带有中文的字符串, 会打印乱码, 原因是传统的遍历方式是按字节遍历, 而一个中文utf8编码对应3个字节
	// 解决方法用切片或者用for range
	str = "love北京"
	str2 := []rune(str) // 把str转换为切片str2
	for i := 0; i < len(str2); i++ {
		fmt.Printf("%c\n", str2[i])
	}
	for index, value := range str {
		fmt.Printf("%d, %c\n", index, value)
	}
	// for range 按照字符遍历
	/*
		0, l
		1, o
		2, v
		3, e
		4, 北
		7, 京
	*/

	// Go语言没有while和do...while语法, 通过for来实现这两个功能

	/*
		1)for循环实现while的效果, 先判断再执行
		for {
		    if 循环条件表达式 {
		        break  // 跳出for循环
		    }
		    循环操作语句
		    循环变量迭代
		}
	*/
	xx := 1
	for {
		if xx > 5 { // 循环条件
			break
		}
		fmt.Println(xx) // 循环操作语句
		xx += 1         // 循环变量迭代
	}
	fmt.Println("最后xx等于", xx) // 6

	/*
		2)for循环实现do..while的效果, 先执行再判断, 至少执行一次循环操作语句
		for {
		    循环操作语句
		    循环变量迭代
		    if 循环条件表达式 {
		        break  // 跳出for循环
		    }
		}
	*/
	yy := 1
	for {
		fmt.Println(yy)
		yy++
		if yy > 5 {
			break
		}
	}
	fmt.Println("最后yy等于", yy) // 6

	// 多重循环控制
	totalSum := 0.
	classNum := 2
	stuNum := 2
	var score float64 // 将score定义在for循环外面, 则score的内存地址不变
	for j := 1; j <= classNum; j++ {
		sum := 0.0
		//var score float64  // 每一次内层循环, score的内存地址是一样的, 每一次外层循环score的内存地址不一样
		for i := 1; i <= stuNum; i++ {
			//var score float64  // 在这里也可以使用, 每一次内循环的score的内存地址都不一样, 只是内层循环结束后无法使用该变量score
			fmt.Printf("请输入第%d班第%d个学生的成绩: ", j, i)
			_, _ = fmt.Scanln(&score)
			fmt.Println(&score)
			sum += score
		}
		fmt.Printf("第%d班的平均成绩是%f\n", j, sum/float64(stuNum))
		totalSum += sum
	}
	fmt.Println("所有班级的平均分是", totalSum/float64(stuNum*classNum))

	// 打印金字塔经典案例, 程序接收一个整数, 表示层数
	/*
		第一种
		*
		**
		***
		****
		*****
	*/
	layer := 10
	for i := 1; i <= layer; i++ {
		for j := 1; j <= i; j++ {
			fmt.Print("*")
		}
		fmt.Println()
	}

	/*
		第二种
		    *
		   ***
		  *****
		 *******
		*********
	*/
	for i := 1; i <= layer; i++ {
		for j := 1; j <= i+layer; j++ {
			if j <= layer+1-i {
				fmt.Print(" ")
			} else {
				fmt.Print("*")
			}
		}
		fmt.Println()
	}

	// 如果不给rand设置随机种子, 生成的随机数不会变
	// time.Now().Unix()返回从1970年1月1日到现在的秒数
	rand.Seed(time.Now().Unix())
	counter := 0
	for {
		rd := rand.Intn(100) + 1 // Intn(n)返回一个从[0, n)不包括n的整数
		counter++
		if rd == 99 {
			fmt.Println("找到了", rd)
			break
		} else {
			fmt.Println(rd)
		}
	}
	fmt.Println(counter)

	// break的注意事项和使用细节
	// 1) break语句出现在多层嵌套的语句块中, 可以通过标签指明要终止的是哪一块语句层, 如果不指定, 则跳出当前最近的一层for
label1: // 设置一个标签, 要加:
	for i := 0; i < 4; i++ {
		//label2:  // 设置一个标签, 要加:
		for j := 0; j < 5; j++ {
			if j == 2 {
				break label1 // 可以跳出外层for循环
				//break label2 跳出内层for循环
			}
			fmt.Println("j =", j)
		}
	}
	fmt.Println("------------------------")

	// 跳转控制语句continue
	// 用于结束本次循环, 继续执行下一次循环
	for i := 0; i < 3; i++ {
		for j := 0; j < 5; j++ {
			if j == 2 {
				continue
			}
			fmt.Println("j =", j)
		}
		fmt.Println(1234)
	}
	/*
		j = 0
		j = 1
		j = 3
		j = 4
		1234
		j = 0
		j = 1
		j = 3
		j = 4
		1234
		j = 0
		j = 1
		j = 3
		j = 4
		1234
	*/
	fmt.Println("------------------------")
	// continue语句出现在多层嵌套的循环语句中, 可以通过标签指明要跳过的是哪一层循环
label3:
	for i := 0; i < 3; i++ {
		for j := 0; j < 5; j++ {
			if j == 2 {
				continue label3 // 跳到外层for循环的下一个循环
			}
			fmt.Println("j =", j)
		}
		fmt.Println(1234) // 不会被打印, 被continue跳过
	}
	/*
		j = 0
		j = 1
		j = 0
		j = 1
		j = 0
		j = 1
	*/

	// 跳转控制语句goto
	// 1) Go语言的goto语句可以无条件地转移到程序中指定的行
	// 2) goto语句通常与条件语句配合使用, 可用来实现条件转移, 跳出循环体等功能
	// 3) 在Go程序设计中一般不主张使用goto语句, 以避免造成程序流程的混乱
	/*
		基本语法:
		goto label
		...
		label: statement
	*/
	xxx := 10
	fmt.Println("ok1")
	if xxx > 2 {
		goto label4
	}
	fmt.Println("ok2")
	fmt.Println("ok3")
	fmt.Println("ok4")
label4:
	fmt.Println("ok5")
	fmt.Println("ok6")
	fmt.Println("ok7")

}
